﻿using System;
using System.Collections.Generic;
using System.Linq;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.Metadata.Entity;
using static T.FAS.Runtime.Metadata.Entity.DataModel.CompositeDataObjectRefSchemaEntity;

namespace T.FAS.Runtime.Metadata.Entity.DataModel
{

    /// <summary>
    /// 具体工厂-CDO
    /// </summary>
    public partial class CompositeDataObject
    {
        public const string RefSchemaPrefix = "Schema.CDO";
        public const string RefSchemaNodeAlias = "Nodes";
        public const string RefSchemaElementAlias = "Elements";

        private const string IsSDO                      = CommonConst.TAttributeExtProperties.IsSDO;
        private const string IsStateField               = CommonConst.TAttributeExtProperties.IsStateField;
        private const string IsTimestamp                = CommonConst.TAttributeExtProperties.IsTimestamp;
        private const string IsSystem                   = CommonConst.TAttributeExtProperties.IsSystem;
        private const string IsParalanguage             = CommonConst.TAttributeExtProperties.IsParalanguage;
        private const string IsMaskingColumn            = CommonConst.TAttributeExtProperties.IsMaskingColumn;
        private const string OriginMaskingColumn        = CommonConst.TAttributeExtProperties.OriginMaskingColumn;
        private const string MaskRule                   = CommonConst.TAttributeExtProperties.MaskRule;
        private const string IsInternational            = CommonConst.TAttributeExtProperties.IsInternational;
        private const string IsEnableMasking            = CommonConst.TAttributeExtProperties.IsEnableMasking;
        private const string IsEnableColumnPermission   = CommonConst.TAttributeExtProperties.IsEnableColumnPermission;

        /// <summary>
        /// 构建TSchema
        /// </summary>
        /// <returns></returns>
        public TSchema BuildSchema()
        {
            TSchema schema = new TSchema
            {
                MetadataType = TEntityType.CDO,
                Entity = BuildTEntity(this.RootCompositeNodeObject),
            };

            return schema;
        }

        public override IRefSchemaEntity BuildRefSchema()
        {
            try
            {
                CompositeDataObjectRefSchemaEntity cdoSchema = new CompositeDataObjectRefSchemaEntity
                {
                    ID = MetaDataID,
                    Code = MetaDataCode,
                    Name = MetaDataName,
                };
                var rootNode = RootCompositeNodeObject;
                if (string.IsNullOrEmpty(rootNode.DataObjectTableAlias))
                    throw new Exception($"CDO:{Code},branchid:{ProjectBranchID}元数据内容有异常，检查节点:{rootNode.ID}的DataObjectTableAlias内容");
                cdoSchema.Nodes = new Dictionary<string, Node> { { rootNode.DataObjectTableAlias, BuildNodes(this, rootNode) } };
                return cdoSchema;
            }
            catch (Exception e)
            {
                throw new Exception($"cdo:{Code}构造RefSchema失败, msg:{e.Message}", e);
            }
        }

        /// <summary>
        /// 递归将树结构CNO转化为以TSchema为入口的VM结构（只有主节点CNO会转化为入口TSchema)
        /// </summary>
        /// <param name="cno">当前节点</param>
        /// <returns>递归时用到的返回值，调用方不需要直接用</returns>
        private TEntity BuildTEntity(CompositeNodeObject cno, string parentNodeRefSchemaExpr = null)
        {
            try
            {
                var outerAssos = cno.Associations.Where(o => o.AssociateType == AssociateType.OuterLeftJoin).ToList();
                #region 当前CNO节点转换为TEntity
                TEntity entity = new TEntity
                {
                    MetadataID = cno.ID,                        //CNO.ID
                    MetadataCode = cno.DataObjectTableAlias,    //结果集表名，设计时已配好
                    MetadataName = cno.Name,                    //CNO.Name
                    MetadataType = TEntityType.DO,              //类型都设置为DO
                    Description = cno.Name,                     //
                    Attributes = new List<TAttribute>(),        //结果集Table列集合，映射CNO Elements
                };
                //处理cno的引用路径表达式
                if (cno.IsRootObject)
                {
                    entity.RefSchemaExpr = $"{RefSchemaPrefix}.{Code}.{RefSchemaNodeAlias}.{cno.DataObjectTableAlias}";
                }
                else
                {
                    entity.RefSchemaExpr = $"{parentNodeRefSchemaExpr}.{RefSchemaNodeAlias}.{cno.DataObjectTableAlias}";
                }
                //将列cno.Elements映射为结果集中的列
                foreach (var ele in cno.Elements.OrderBy(o => o.Sort))
                {
                    DataColumn col = ele.GetDataColumn(cno);
                    if (ele.ElementType == ElementType.Normal && col == null)
                        throw new Exception($"CDO:{this.Code} BuildSchema失败，{(cno.IsRootObject ? "主" : "子")}节点:{cno.ID}中找不到元素:{ele.Alias}对应的DO列:{ele.DataColumnID}");

                    TAttribute attr = new TAttribute
                    {
                        ID = ele.ID,                                                                    //【暂时用不到】
                        Code = ele.Alias ?? ele.Name,                                                   // 结果集列名，默认为Alias
                        Name = ele.DisplayName,                                                         // 展示用中文名
                        RefSchemaExpr = $"{entity.RefSchemaExpr}.{RefSchemaElementAlias}.{(ele.Alias ?? ele.Name)}",   //引用路径表达式
                        DataType = ele.DataType,                                                        // 数据类型
                        IsRequired = !ele.IsAllowNull,                                                  // 是否必填，跟IsRequired相反
                        DefaultValue = ele.DefaultValue,                                                // 默认值，表达式或常量直接透给前端
                        Length = ele.Length,                                                            // 长度
                        Precision = ele.Precision,                                                      // 精度
                        Description = col?.Description,                                                 // DO列说明
                        IsPrimaryKey = col != null && col.IsPkColumn,                                   // 是否主键 
                        SortOrder = ele.Sort,                                                           // 排序
                    };
                    if (ele.ElementType == ElementType.Reference)
                    {
                        attr.AttributeType = TAttributeType.ForeignRelatedAttribute;
                        #region 根据外键关联关系Asso，将当前的cno节点外键关联字段标记为是否国际化
                        if (outerAssos != null)
                        {
                            foreach (var outerAsso in outerAssos)
                            {
                                if (outerAsso.AssoDataObject != null)
                                {
                                    var sourColumnID = outerAsso.RefElements?.Where(r => r.TargetElementID == ele.ID).FirstOrDefault()?.SourceElementID;
                                    if (!string.IsNullOrEmpty(sourColumnID))
                                    {
                                        var sourColumn = outerAsso.AssoDataObject.Columns.FirstOrDefault(c => c.ID == sourColumnID);
                                        if (sourColumn != null)
                                        {
                                            if (attr.ExtProperties == null) 
                                                attr.ExtProperties = new Dictionary<string, object>();
                                            attr.ExtProperties[IsInternational] = sourColumn.IsInternational;
                                            attr.ExtProperties[IsEnableMasking] = sourColumn.MaskingRule.IsEnable;
                                            attr.ExtProperties[IsEnableColumnPermission] = sourColumn.IsEnableColumnPermission;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        #endregion
                    }
                    else if (ele.ElementType == ElementType.Normal)
                    {
                        if (col != null)
                        {
                            //对于CDO直接引用的DO列，表达式直接改为依赖DO的expr。（此处不会出现SDO，因为CDO主子节点中不会出现DO)
                            var expr = $"{DataObject.RefSchemaPrefixDO}.{cno.DataObject.Code}.{DataObject.RefSchemaColumnAlias}.{(ele.Alias ?? ele.Name)}";
                            attr.RefSchemaExpr = expr;
                            attr.ExtProperties = new Dictionary<string, object>();
                            if (col.IsFkColumn)
                            {
                                attr.AttributeType = TAttributeType.ForeignKey;
                                if (col.IsRefDataObject)
                                {
                                    attr.ExtProperties[IsSDO] = false;
                                    attr.ExtProperties[IsStateField] = false;
                                }
                                else
                                {
                                    attr.ExtProperties[IsSDO] = true;
                                    if (ele.StateMachines == null || ele.StateMachines.Count == 0)
                                        attr.ExtProperties[IsStateField] = false;
                                    else
                                        attr.ExtProperties[IsStateField] = true;
                                }
                            }
                            else
                            {
                                attr.AttributeType = TAttributeType.Normal;
                            } 
                            attr.ExtProperties[IsTimestamp] = col.IsTimestamp;
                            attr.ExtProperties[IsSystem] = col.IsSystem;
                            attr.ExtProperties[IsInternational] = col.IsInternational;
                            attr.ExtProperties[IsEnableMasking] = col.MaskingRule.IsEnable;
                            attr.ExtProperties[MaskRule] = col.MaskingRule;
                            attr.ExtProperties[IsEnableColumnPermission] = col.IsEnableColumnPermission;
                            if (ele.TryGetDataColumnHelpSetting(cno, out DataObjectColumnHelpSetting setting))
                            {
                                attr.ExtProperties[nameof(DataObjectColumnHelpSetting)] = setting;
                            }
                        }
                    }
                    entity.Attributes.Add(attr);

                    if (ele.ElementType == ElementType.Normal && col.IsInternational)
                    {
                        var mapping = InternationalDataTools.LanguageMapping;
                        if (mapping.Count > 0)
                        {
                            foreach (var langKey in mapping.Keys)
                            {
                                string logicTableFieldName = InternationalTools.GetLogicFieldName(col.ColumnName, mapping[langKey]);
                                TAttribute i18NAttr = attr.CloneAttr();
                                i18NAttr.Code = logicTableFieldName;
                                i18NAttr.Name += InternationalTools.GetLogicFieldNameSuffix(mapping[langKey]);
                                i18NAttr.ExtProperties[IsParalanguage] = true;
                                entity.Attributes.Add(i18NAttr);
                            }
                        }
                    }

                    //处理掩码&权限
                    if (ele.ElementType == ElementType.Normal && col.MaskingRule.IsEnable)
                    {
                        TAttribute maskAttr = attr.CloneAttr();
                        maskAttr.Code = DataColumn.GetMaskColumnName(cno.DataObject, col.ColumnName);
                        maskAttr.Name += col.DisplayName + "掩码";
                        maskAttr.ExtProperties[IsMaskingColumn] = true;
                        maskAttr.ExtProperties[OriginMaskingColumn] = ele.Alias ?? ele.Name;
                        maskAttr.ExtProperties[MaskRule] = col.MaskingRule;
                        entity.Attributes.Add(maskAttr);
                    }
                }
                #endregion

                #region 根据主子关系Asso，填充Relation部分（主子关系只能有一个，即节点只有一个父级）
                var innerAsso = cno.Associations.Find(o => o.AssociateType == AssociateType.InnerJoin);
                if (innerAsso != null)
                {
                    var assoItem = innerAsso.Items.FirstOrDefault();
                    if (assoItem != null)
                    {
                        entity.Relation = new TEntityRelation
                        {
                            ID = assoItem.ID,   //暂时用不到
                            FromTEntityCode = cno.ParentObject.DataObjectTableAlias, //父结果集表名
                            FromTAttributeCode = GetPKColumnName(cno.ParentObject),  //父结果集主键列在结果集中的列名
                            ToTEntityCode = entity.MetadataCode,                     //当前节点的结果集的表名 Table Name
                            ToTAttributeCode = GetMappingColumnNameByEleId(cno, assoItem.SourceElementID) //当前节点外键列的映射列名
                        };
                    }
                }
                #endregion

                #region 遍历CNO子节点集合，补充父节点Property信息
                //通过子类Asso属性，给当前节点创建虚拟属性指向子类集合
                foreach (var child in cno.ChildCompositeNodeObjects.OrderBy(o => o.ID))
                {
                    bool isList = IsList(child);
                    DataType dataType;
                    string description = string.Empty;
                    if (isList)
                    {
                        dataType = DataType.List;
                        description = $"{child.DataObject.Name}集合";
                    }
                    else
                    {
                        dataType = DataType.Record;
                        description = $"{child.DataObject.Name}记录";
                    }

                    string childNodeCode = TUtil.GetPropertyName(child.DataObjectTableAlias, isList);
                    TAttribute childProp = new TAttribute
                    {
                        ID = Guid.NewGuid().ToString(),                                         //【暂时用不到】
                        Code = childNodeCode,                                                        // ***这里的属性名约定为子节点的DO.Code，重名情况在设计时已处理
                        Name = child.DataObject.Name,                                           // 子集的Name
                        RefSchemaExpr = $"{entity.RefSchemaExpr}.{child.DataObjectTableAlias}", // 引用路径表达式
                        SortOrder = entity.Attributes.Count + 1,                                // 排序
                        Description = description,                                              // DO列说明
                        IsPrimaryKey = false,                                                   // 是否主键
                        IsDetail = false,                                                       //【待定】
                        RelatePropertyCode = entity.Attributes.Find(o => o.IsPrimaryKey).Code,  // 当前数据源的主键列Code
                        DataType = dataType,                                                    // 根据DOR关系判断是List还是Record
                    };
                    /*********递归生成子节点SubSchema*********/
                    childProp.Entity = BuildTEntity(child, entity.RefSchemaExpr);
                    childProp.Entity.ResultType = childProp.DataType;
                    entity.Attributes.Add(childProp);
                }
                #endregion

                return entity;
            }
            catch (Exception e)
            {
                throw new Exception($"CDO:{cno.CompositeDataObjectID} BuildSchema失败，errMsg:{e.Message}，stackTrace:{e.StackTrace}", e);
            }
        }

        /// <summary>
        /// 递归将树结构CNO转化为以TSchema为入口的VM结构（只有主节点CNO会转化为入口TSchema)
        /// </summary>
        /// <param name="cno">主节点CNO</param>
        /// <returns>递归时用到的返回值，调用方不需要直接用</returns>
        private Node BuildNodes(CompositeDataObject cdo, CompositeNodeObject cno)
        {
            Node node = Node.Mapping(cdo, cno);
            List<CompositeNodeObject> childs = cdo.CompositeNodeObjects.Where(n => n.ParentObjectID == cno.ID).ToList();
            if (childs != null && childs.Count > 0)
            {
                node.Nodes = new Dictionary<string, Node>();
                foreach (var childNode in childs)
                {
                    node.Nodes[childNode.DataObjectTableAlias] = BuildNodes(cdo, childNode);
                }
            }
            return node;
        }

        #region 帮助方法
        /// <summary>
        /// 找到当前节点跟父节点是1:1 还是 1:N的关系，原则上建模时确定
        /// </summary>
        /// <param name="cno"></param>
        /// <returns>true:List, false:Record</returns>
        private bool IsList(CompositeNodeObject cno)
        {
            var parentDOId = cno.ParentObject.DataObjectID;
            var relationType = cno.DataObject.DataObjectRelations
                                .Where(r => r.FromDoID == parentDOId)
                                .Select(o => o.RelationType)
                                .Distinct()
                                .FirstOrDefault();
            return relationType == 0;   //关联关系类型：0: N…1; 1: 1…1
        }

        /// <summary>
        /// 获取指定CNO节点的主键列在结果集中的列名
        /// </summary>
        /// <param name="CNO"></param>
        /// <returns></returns>
        private string GetPKColumnName(CompositeNodeObject CNO)
        {
            foreach (var ele in CNO.Elements.Where(e => e.ElementType == ElementType.Normal))
            {
                var col = ele.GetDataColumn(CNO);
                if (col.IsPkColumn)
                    return ele.Alias ?? ele.Name;
            }
            return null;
        }
         
        /// <summary>
        /// 获取指定节点的指定Ele的映射列名
        /// </summary>
        /// <param name="CNO"></param>
        /// <param name="eleId"></param>
        /// <returns></returns>
        private string GetMappingColumnNameByEleId(CompositeNodeObject CNO, string eleId)
        {
            var ele = CNO.Elements.Find(element => element.ID == eleId);
            return ele.Alias ?? ele.Name;
        }

        #endregion
    }
}
